# THIS IS A SOURCE CODE FILE FROM I'M NOT EVEN HUMAN THE GAME.
# IT COULD BE USED IN A DIFFERENT PIECE OF SOFTWARE ( LIKE A
# DIFFERENT GAME ), BUT IT WAS ORIGINALLY WRITTEN FOR I'M NOT
# EVEN HUMAN THE GAME.

# THE DEVELOPERS OF THE GAME ARE : (C) J.Y.AMIHUD, AYYZEE AND 
# OTHER CONTRIBUTORS. THIS AND OTHER FILES IN THIS GAME,
# UNLESS SPECIFICALLY NOTED, COULD BE USED UNDER THE TERMS OF
# GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER VERSION.

import os
import datetime # For the FPS meter mainly

# GTK module ( Graphical interface
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
import cairo
import threading


# Engine submodules ( Layers )
from modules import ui

# Game scenes ( layers )
from modules import testing
from modules import main_menu
from modules import settings
from modules import editor
from modules import world
from modules import gameplay

def previous(game):

    """This function is making an exact copy of game.current 
       to preserve the values from the previous frame."""
    
    game.previous = {}
    for i in game.current:
        if type(game.current[i]) == list or type(game.current[i]) is dict:
            game.previous[i] = game.current[i].copy()
        else:
            game.previous[i] = game.current[i]

def run():

    """This function will launch the game's engine and make a
       window and everything."""

    # This is a hack. game will be a Gtk.Window() object. But I
    # am going to add into it a lot of other things and pass it
    # to all the sub-functions as a kind of container for semi-
    # global variables. Anything important goes into the game
    # variable.
    
    game = Gtk.Window()
    game.set_default_size(1200,720)
    game.set_position(Gtk.WindowPosition.CENTER)
    game.connect("destroy", Gtk.main_quit)
    game.set_default_icon_from_file("icon.png")
    game.set_title("I'm Not Even Human - The Game")

    # Catching events from keys and mouse clicks

    game.connect("button-press-event", mouse_button_press, game)
    game.connect("button-release-event", mouse_button_release, game)
    game.connect("key-press-event", key_press, game)
    game.connect("key-release-event", key_release, game)
    
    # To catch the scroll events from the mouse wheel in GTK I
    # need to have a scrolled windows widget. There is no use
    # for it. I'm making it only to add this functionality
    # which is just an another hack.

    scroll = Gtk.ScrolledWindow()
    scroll.connect("scroll-event", scrolling, game)

    game.scene    = "main_menu" # Current menu of the game
    game.map      = {}          # Chunks of the map
        
    game.current  = {} # Values for the current frame
    game.previous = {} # Values for the previous frame
    game.images   = {} # All images loaded to memory
    game.menus    = {} # Metada to navigate buttons using a keyboard
    game.FPS      = 0  # Frames per second of the game.
    game.AFPS     = 0  # Average frames per second
    game.scroll   = {} # Scrolls

    settings.update_worlds(game) # Worlds metadata  | game.worlds
    settings.load_settings(game) # Loading settings | game.settings
    world.update_elements(game)  # Loading assets   | game.elements
    
    ui.load_palete(game)  # game.palete ( colors from assets/palete.json )
    
    game.current["frame"]     = 0     # The number of frames from launch
    game.current["camera"]    = [0,0,0]       # Game camera
    game.current["testing"]   = False # Whether the devmode is on / off
    game.current["LMB"]       = False # Left Mouse Button
    game.current["MMB"]       = False # Middle Mouse Button
    game.current["RMB"]       = False # Right Mouse Button
    game.current["keys"]      = []    # List of pressed keys ( IDs )
    game.current["scroll"] = [0,0]

    # The same keys can have multiple IDs depending on what type of
    # keyboard you have or where on the keyboard they are located.
    # These arrays store the IDs of common keys.
    game.current["ctrl"] = [65507, 65508]       # [CTRL]
    game.current["plus"] = [61, 43, 65451]      # [+]
    game.current["minus"] = [45, 95, 65453]     # [-]


    # States of various dynamic stuff in the game
    #                      [Object]    [Potision]      [Condition]
    game.current["state"] = {"4211D79": {"xyz":[0,0,0], "colision": False}}

    
    # Making the copy of all current, so far into previous
    previous(game)

    # Setting up the FPS meter
    game.sFPS = datetime.datetime.now()

    # Setting the drawable
    gamedraw = Gtk.DrawingArea()
    gamedraw.set_size_request(800, 600)
    scroll.set_size_request(800, 600) 
    game.add(scroll)                   
    scroll.add_with_viewport(gamedraw)    
    gamedraw.connect("draw", gamedrawing, game)

    # Running

    game.show_all()
    Gtk.main()

def gamedrawing(gamedrawing, main_layer, game):

    """You can think of this function as one that combines various
       layers into a finished image. And handles some logic related
       to it."""

    
    # FPS counter
    game.fFPS = datetime.datetime.now()
    game.tFPS = game.fFPS - game.sFPS
    game.FPS = int ( 1.0 / ( game.tFPS.microseconds /1000000))
    if game.current["frame"] % 30 == 0:
        game.AFPS = game.FPS
    game.sFPS = datetime.datetime.now()
        
    # Updating the frame
    game.current["frame"] += 1
    
    # Getting mouse data about the frame
    game.current['mx'] = game.get_pointer()[0] / game.settings["pixels"]
    game.current['my'] = game.get_pointer()[1] / game.settings["pixels"]
    game.current['w']  = int(round(game.get_size()[0] / game.settings["pixels"] ))
    game.current['h']  = int(round(game.get_size()[1] / game.settings["pixels"] ))

    if game.current["frame"] == 1:
        previous(game)
    
    
    # Switching the fullscreen mode on and off. Ctrl-F
    # Warning! The method used to fullscreen bypasses and disobeys some window manager
    # rules.
    
    #  [CTRL]                            [F]
    if 65507 in game.current["keys"] and 102 in game.current["keys"]:
        game.settings["fullscreen"] = not game.settings["fullscreen"]
        game.current["keys"].remove(102)

    if game.settings["fullscreen"]:
        game.fullscreen()
    else:
        game.unfullscreen()

    layers = [] # Layers to draw


    if game.scene == "main_menu":
        layers.append(main_menu.layer(game))

    elif game.scene == "settings":
        layers.append(settings.layer(game))

    elif game.scene == "editor":
        layers.append(editor.layer(game))

    elif game.scene == "gameplay":
        layers.append(gameplay.layer(game))
    

    # Check if any of the keys are currently being pressed by comparing them
    # to the ones that are present in their respective arrays.
    # [KEYS]          [ARRAY that has the common key IDs]   [ARRAY that has ALL of the key IDs]
    ctrlDown = any(item in game.current["ctrl"] for item in game.current["keys"]) # Check if any CTRL key is pressed.
    plusDown = any(item in game.current["plus"] for item in game.current["keys"]) # Check if any + key is pressed.
    minusDown = any(item in game.current["minus"] for item in game.current["keys"]) # Check if any - key is pressed.

    # Switching the testing mode on and off. Ctrl-T
    #  [CTRL]                            [T]
    if ctrlDown and 116 in game.current["keys"]:
        game.current["testing"] = not game.current["testing"]
        game.current["keys"].remove(116)
        


    if game.current["testing"]:
        layers.append(testing.layer(game))

    # Scaling the layer to the window size
    main_layer.scale(game.settings["pixels"],
                    game.settings["pixels"])

    ui.color(game, main_layer, "black")
    main_layer.rectangle(0,0,
                        game.current["w"],
                        game.current["h"])
    main_layer.fill()

    
    for layer in layers:


        # Setting the layer as a kind of brush
        main_layer.set_source_surface(layer, 0 , 0)

        # This is making the pixel art look pixalated
        p = main_layer.get_source()
        p.set_filter(cairo.FILTER_NEAREST)
        
        # Painting the layer
        main_layer.paint()

    # Update things when window is stretched
    if game.current["h"] != game.previous["h"]\
    or game.current["w"] != game.previous["w"]:
        for i in game.menus:
            if "buttons" in  game.menus[i]:
                game.menus[i]["buttons"] = []

    if 65307 in game.current["keys"]:
        game.scene = "main_menu"

#      [CTRL]        [+]
    if ctrlDown and plusDown:
        game.settings["pixels"] += 1
        #save_settings(game)
        for key in game.current["plus"]:
            try:
                game.current["keys"].remove(key)
            except:
                pass


#      [CTRL]        [-]
    if ctrlDown and minusDown and game.settings["pixels"] >1:
        game.settings["pixels"] -= 1
        #save_settings(game)
        for key in game.current["minus"]:
            try:
                game.current["keys"].remove(key)
            except:
                pass

                
    previous(game) # Go back to the previous screen

    # Refreshing the frame automatically
    gamedrawing.queue_draw()
  


# Mouse
def mouse_button_press(widget, event, game):

    # This function marks activation of the button. Not it's deactivation.

    for i, button in enumerate(["LMB", "MMB", "RMB"]):
        if i+1 == int(event.get_button()[1]):
            game.current[button] = [event.x / game.settings["pixels"],
                                    event.y / game.settings["pixels"]]
    
def mouse_button_release(widget, event, game):
    
    # This function reverses the effects of the mouse_button_press() function.
    
    for i, button in enumerate(["LMB", "MMB", "RMB"]):
        if i+1 == int(event.get_button()[1]):
            game.current[button] = False


def key_press(widget, event, game):
    if event.keyval not in game.current["keys"]:
        game.current["keys"].append(event.keyval)
        game.current["key_letter"] = event.string
        
def key_release(widget, event, game):

    # Key values represent an ASCII code of the key's character
    # So 'A' is 65 and 'a' is 97. To mitigate sticky keys when
    # Shift is pressed, we need to remove all versions of the keys.

    upper = ord( chr( event.keyval ).upper() ) # Uppercase version
    lower = ord( chr( event.keyval ).lower() ) # Lowercase version

    for i in (upper, lower, event.keyval):
        if i in game.current["keys"]:
            game.current["keys"].remove(i)    
    
    # I also want to clean the key letter. Because other wise in the
    # script writer I had weird key presses all the time. 
    if not game.current["keys"]:    
        game.current["key_letter"] = ""
    
def scrolling(widget, event, game):
    e, x, y = event.get_scroll_deltas()
    game.current["scroll"] = [x,y]
